SPDX-FileCopyrightText: 2026 Salma Mouma SPDX-FileCopyrightText: 2026 AlICe laboratory https://alicelab.be
SPDX-License-Identifier: GPL-3.0-or-later
import math
import os
import random
import bpySALMA MOUMA ---------------------- REVOLUTION 909 - DAFT PUNK Jury Final Juin 2026 NB : Unable Cell Fracture Add-On
---------------------------------------CLEARING-----------------------------------------#
os.system("clear")def clean_that_up():
bpy.ops.object.select_all(action="SELECT")
bpy.data.objects["Oblique Camera"].select_set(False)
bpy.ops.object.delete()
bpy.ops.outliner.orphans_purge()
clean_that_up()---------------------------------------CLEARING-----------------------------------------#
tout mettre dans la collection fracture_2
if "fracture_2" not in bpy.data.collections:
fracture_coll = bpy.data.collections.new("fracture_2")
bpy.context.scene.collection.children.link(fracture_coll)
else:
fracture_coll = bpy.data.collections["fracture_2"]A : Creation du fracture cube ################################################################
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), size=0.1)
obj = bpy.context.active_object
obj.name = "Fracture_Cube"Subdivision
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.subdivide(number_cuts=10)
bpy.ops.object.mode_set(mode="OBJECT")B : Lancement du cell fracture
bpy.ops.object.add_fracture_cell_objects(source={"PARTICLE_OWN"})on les stocke dans une liste
fractured_objects = [obj for obj in bpy.context.selected_objects if "cell" in obj.name]C : Supprimer le fracture cube
if "Fracture_Cube" in bpy.data.objects:
bpy.data.objects.remove(bpy.data.objects["Fracture_Cube"], do_unlink=True)D : Randomizing
for block in fractured_objects:Position random dans le cube de 10cm (-0.05m à 0.05m) + taille random
s = random.uniform(0.9, 1)
block.scale = (s, s, s)def is_corner_fragment(obj, threshold=0.03):
loc = obj.location
return (
abs(abs(loc.x) - 0.05) < threshold
and abs(abs(loc.y) - 0.05) < threshold
and abs(abs(loc.z) - 0.05) < threshold
)filtrer les fragments NON coins
non_corner_blocks = [obj for obj in fractured_objects if not is_corner_fragment(obj)]Randomizing le nombre à supprimer
nb_to_delete = min(random.randint(65, 95), len(non_corner_blocks))
for _ in range(nb_to_delete):
block_to_remove = random.choice(non_corner_blocks)
non_corner_blocks.remove(block_to_remove)
fractured_objects.remove(block_to_remove)
bpy.data.objects.remove(block_to_remove, do_unlink=True)tout ranger dand la collection fracture_2
for obj in bpy.data.objects:
name_low = obj.name.lower()
if "cell" in name_low or "corner" in name_low:
if obj.name not in fracture_coll.objects:
fracture_coll.objects.link(obj)On evite le doublage
for coll in obj.users_collection:
if coll != fracture_coll:
coll.objects.unlink(obj)Objectif: structure physique qui tiens les fragments representants les couches
objet support
bpy.ops.mesh.primitive_cube_add(size=0.1, location=(0, 0, 0))
geo_obj = bpy.context.active_object
geo_obj.name = "GN_Structure"Cet objet ne doit pas etre dans la meme collection que les autres
if geo_obj.name in bpy.data.collections["fracture_2"].objects:
bpy.data.collections["fracture_2"].objects.unlink(geo_obj)On le remet dans la collection principale si besoin
if geo_obj.name not in bpy.context.scene.collection.objects:
bpy.context.scene.collection.objects.link(geo_obj)modifier
mod = geo_obj.modifiers.new(name="GeometryNodes", type="NODES")node tree - nom
node_group = bpy.data.node_groups.new("StructureNodes", "GeometryNodeTree")
mod.node_group = node_group
nodes = node_group.nodes
links = node_group.linksclear nodes
nodes.clear()creer l OUTPUT - first step
node_group.interface.new_socket(
name="Geometry",
in_out="OUTPUT",
socket_type="NodeSocketGeometry",
)nodes
group_out = nodes.new("NodeGroupOutput")
collection_info = nodes.new("GeometryNodeCollectionInfo")
instances_to_points = nodes.new("GeometryNodeInstancesToPoints")
cube = nodes.new("GeometryNodeMeshCube")
proximity = nodes.new("GeometryNodeProximity")
set_position = nodes.new("GeometryNodeSetPosition")
merge = nodes.new("GeometryNodeMergeByDistance")
mesh_to_curve = nodes.new("GeometryNodeMeshToCurve")
curve_circle = nodes.new("GeometryNodeCurvePrimitiveCircle")
curve_to_mesh = nodes.new("GeometryNodeCurveToMesh")paramètres de ces nodes
collection_info.inputs["Collection"].default_value = bpy.data.collections["fracture_2"]
collection_info.transform_space = "RELATIVE"
collection_info.inputs["Separate Children"].default_value = True
cube.inputs["Size"].default_value = (0.1, 0.1, 0.1)
cube.inputs[0].default_value = (0.1, 0.1, 0.1) # Size
cube.inputs[1].default_value = 100 # Vertices X
cube.inputs[2].default_value = 100 # Vertices Y
cube.inputs[3].default_value = 100 # Vertices Z
curve_circle.inputs["Radius"].default_value = 0.0015
curve_circle.inputs["Resolution"].default_value = 40
merge.inputs["Distance"].default_value = 0.01
proximity.target_element = "POINTS"connexions entre ces nodes
instances - points
links.new(collection_info.outputs["Instances"], instances_to_points.inputs["Instances"])proximity
links.new(instances_to_points.outputs[0], proximity.inputs[0])cube - base géométrique
links.new(cube.outputs["Mesh"], set_position.inputs["Geometry"])position controlee par proximity
links.new(proximity.outputs["Position"], set_position.inputs["Position"])merge
links.new(set_position.outputs["Geometry"], merge.inputs["Geometry"])mesh - curve
links.new(merge.outputs["Geometry"], mesh_to_curve.inputs["Mesh"])curve - mesh
links.new(mesh_to_curve.outputs["Curve"], curve_to_mesh.inputs["Curve"])
links.new(curve_circle.outputs["Curve"], curve_to_mesh.inputs["Profile Curve"])sortie finale
links.new(curve_to_mesh.outputs["Mesh"], group_out.inputs["Geometry"])UPDATE
bpy.context.view_layer.update()Le code décide aléatoirement si les loops apparaissent ou non (moment de break ou pas)
show_loops = random.choice([True, False])
if show_loops:Creation du cadre
bpy.ops.mesh.primitive_cube_add(size=1, location=(0, 0, 0))
cadre_base = bpy.context.active_object
cadre_base.name = "Cadre"
cadre_base.scale = (0.07, 0.07, 0.005)
bpy.ops.object.transform_apply(scale=True)Cutter cadre pour percer
bpy.ops.mesh.primitive_cube_add(size=1, location=(0, 0, 0))
cutter = bpy.context.active_object
cutter.scale = (0.05, 0.05, 0.1)
bpy.ops.object.transform_apply(scale=True)Boolean
bool_mod = cadre_base.modifiers.new(name="Perçage", type="BOOLEAN")
bool_mod.object = cutter
bool_mod.operation = "DIFFERENCE"
bool_mod.solver = "EXACT"
bpy.context.view_layer.objects.active = cadre_base
bpy.ops.object.modifier_apply(modifier="Perçage")
bpy.data.objects.remove(cutter, do_unlink=True)
cadre_base.hide_set(True)rotation
def rad(deg):
return math.radians(deg)
max_offset = 0.0141 cadre X
new_X = cadre_base.copy()
new_X.data = cadre_base.data.copy()
new_X.name = "Cadre_X"
bpy.context.collection.objects.link(new_X)
new_X.location = (random.uniform(-max_offset, max_offset), 0, 0)
new_X.rotation_euler = (0, rad(90), 0)2 cadre Y
new_Y = cadre_base.copy()
new_Y.data = cadre_base.data.copy()
new_Y.name = "Cadre_Y"
bpy.context.collection.objects.link(new_Y)
new_Y.location = (0, random.uniform(-max_offset, max_offset), 0)
new_Y.rotation_euler = (rad(90), 0, 0)3 cadre Z
new_Z = cadre_base.copy()
new_Z.data = cadre_base.data.copy()
new_Z.name = "Cadre_Z"
bpy.context.collection.objects.link(new_Z)
new_Z.location = (0, 0, random.uniform(-max_offset, max_offset))
new_Z.rotation_euler = (0, 0, 0)Nettoyage final seulement si cadre_base existe
bpy.data.objects.remove(cadre_base, do_unlink=True)
else:
print("Pas de Loops cette fois-ci !!")